package com.liam.lotterysystem.service.mq;

import cn.hutool.core.date.DateUtil;
import com.liam.lotterysystem.common.exception.ServiceException;
import com.liam.lotterysystem.common.utils.JacksonUtil;
import com.liam.lotterysystem.common.utils.MailUtil;
import com.liam.lotterysystem.common.utils.SMSUtil;
import com.liam.lotterysystem.controller.param.DrawPrizeParam;
import com.liam.lotterysystem.dao.dataobject.ActivityPrizeDO;
import com.liam.lotterysystem.dao.dataobject.WinningRecordDO;
import com.liam.lotterysystem.dao.mapper.ActivityPrizeMapper;
import com.liam.lotterysystem.dao.mapper.WinningRecordMapper;
import com.liam.lotterysystem.service.DrawPrizeService;
import com.liam.lotterysystem.service.activitystatus.ActivityStatusManager;
import com.liam.lotterysystem.service.dto.ConvertActivityStatusDTO;
import com.liam.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import com.liam.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import com.liam.lotterysystem.service.enums.ActivityStatusEnum;
import com.liam.lotterysystem.service.enums.ActivityUserStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.liam.lotterysystem.common.config.DirectRabbitConfig.QUEUE_NAME;

/**
 * @Author: LiamLMK
 * @CreateTime: 2024-11-01
 * @Description:
 * @Version: 1.0
 */

@Slf4j
@Component
@RabbitListener(queues = QUEUE_NAME)
public class MqReceiver {

    @Autowired
    private DrawPrizeService drawPrizeService;
    @Autowired
    private ActivityStatusManager activityStatusManager;
    @Qualifier("asyncServiceExecutor")
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    @Autowired
    private MailUtil mailUtil;
    @Autowired
    private SMSUtil smsUtil;
    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;
    @Autowired
    private WinningRecordMapper winningRecordMapper;

    @RabbitHandler
    public void process(Map<String, String> message) throws Exception {
        // 成功接收到队列中的消息
        log.info("MQ成功接收到消息, message: {}", JacksonUtil.writeValueAsString(message));
        String paramString = message.get("messageData");
        DrawPrizeParam param = JacksonUtil.readValue(paramString, DrawPrizeParam.class);
        // 处理抽奖的流程

        try {

            // 校验抽奖请求是否有效
            drawPrizeService.checkDrawPrizeParam(param);

            // 状态扭转处理（重要！！设计模式）
            statusConvert(param);
            // 保存中奖者名单
            List<WinningRecordDO> winningRecordDOList = drawPrizeService.saveWinnerRecords(param);

            // 通知中奖者（邮箱、短信）
            //抽奖之后的后续流程，异步（并发）处理
            syncExecute(winningRecordDOList);

        } catch (ServiceException e) {
            log.error("处理MQ消息异常, {}:{}", e.getCode(), e.getMessage(), e);
            // 如果异常，需要保证事务一致性（回滚）
            rollback(param);
            // 抛出异常：消息重试（解决异常：代码bug、网络问题、服务器问题...）
            throw e;

        } catch (Exception e) {
            log.error("处理MQ消息异常", e);
            // 如果异常，需要保证事务一致性（回滚）
            rollback(param);
            // 抛出异常
            throw e;
        }

    }

    /**
     * 处理抽奖异常的回滚行为：恢复处理请求之前的库表状态
     *
     * @param param
     */
    private void rollback(DrawPrizeParam param) {

        // 1、回滚状态：活动、奖品、人员
        // 判断状态是否需要回滚
        if (!statusNeedRollBack(param)) {
            // 不需要：return
            return;
        }

        // 需要：回滚操作
        rollbackStatus(param);

        // 2、回滚中奖者名单
        // 是否需要回滚
        if (winnerNeedRollBack(param)) {
            // 不需要：return
            return;
        }

        // 需要：回滚操作
        rollBackWinner(param);
    }

    private boolean statusNeedRollBack(DrawPrizeParam param) {
        // 判断活动 + 奖品 + 人员表相关状态 是否已经扭转（正常思路）
        // 扭转状态时，保证了事务一致性，即要么都扭转，要么都没扭转（不包含活动）：
        // 因此，只用判断人员/奖品是否扭转过，就能判断出状态是否扭转
        // 不能判断活动是否已经扭转（活动是根据奖品完成度）

        // 结论：判断奖品状态是否扭转，就能判断出全部状态是否扭转
        ActivityPrizeDO activityPrizeDO =
                activityPrizeMapper.selectAPId(param.getActivityId(), param.getPrizeId());
        if (activityPrizeDO.getStatus()
                .equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETED.name())) {
            // 运行到此处，表明已经扭转，需要回滚
            return true;
        }
        return false;
    }

    /**
     * 恢复相关状态
     *
     * @param param
     */
    private void rollbackStatus(DrawPrizeParam param) {
        // 涉及状态的恢复工作，使用ActivityStatusManager
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.RUNNING);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.INIT);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.INIT);

        activityStatusManager.rollBackHandlerEvent(convertActivityStatusDTO);
    }

    /**
     * @param param
     * @return
     */
    private boolean winnerNeedRollBack(DrawPrizeParam param) {
        // 判断活动中奖品是否存在中奖者
        int count = winningRecordMapper.countByAPID(param.getActivityId(), param.getPrizeId()); // 在这个活动下的奖品下 是否有中奖名单
        return count > 0;
    }

    /**
     * 回滚中奖记录：删除奖品下中奖者
     * @param param
     */
    private void rollBackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecords(param.getActivityId(), param.getPrizeId());
    }

    /**
     * 并发处理抽奖后续流程
     *
     * @param winningRecordDOList
     */
    private void syncExecute(List<WinningRecordDO> winningRecordDOList) {
        // 通过线程池处理异步任务 threadPoolTaskExecutor
        // 可拓展：加入策略模式或其他设计模式来完成后续的异步操作

        // 短信通知
        threadPoolTaskExecutor.execute(() -> sendMessage(winningRecordDOList));
        // 邮件通知
        threadPoolTaskExecutor.execute(() -> sendEmail(winningRecordDOList));
        // ...
    }

    /**
     * 发送邮件
     *
     * @param winningRecordDOList
     */
    private void sendEmail(List<WinningRecordDO> winningRecordDOList) {
        if (CollectionUtils.isEmpty(winningRecordDOList)) {
            log.info("中奖列表为空，无需发送邮件");
            return;
        }

        for (WinningRecordDO winningRecordDO : winningRecordDOList) {
            // e.g. Hi,胡一博。恭喜你在抽奖活动活动中获得二等奖:吹风机。获奖奖时间为18:18:44,请尽快领取您的奖励
            String context = "Hi，" + winningRecordDO.getWinnerName() + "。恭喜你在"
                    + winningRecordDO.getActivityName() + "活动中获得"
                    + ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage()
                    + "：" + winningRecordDO.getPrizeName() + "。获奖时间为"
                    + DateUtil.formatTime(winningRecordDO.getWinningTime()) + "，请尽快领取您的奖励！";

            // sendSampleMail方法已经进行了异常的捕获，单次异常不会影响后续邮件的发送
            mailUtil.sendSampleMail(winningRecordDO.getWinnerEmail(),
                    "中奖通知",
                    context);
        }
    }

    /**
     * 发送短信
     *
     * @param winningRecordDOList
     */
    private void sendMessage(List<WinningRecordDO> winningRecordDOList) {
        if (CollectionUtils.isEmpty(winningRecordDOList)) {
            log.info("中奖列表为空，无需发送短信");
            return;
        }

        for (WinningRecordDO winningRecordDO : winningRecordDOList) {
            Map<String, String> map = new HashMap<>();
            map.put("name", winningRecordDO.getWinnerName());
            map.put("activityName", winningRecordDO.getActivityName());
            map.put("prizeTiers", ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage());
            map.put("prizeName", winningRecordDO.getPrizeName());
            map.put("winningTime", DateUtil.formatTime(winningRecordDO.getWinningTime()));

            smsUtil.sendMessage("SMS_475305138",
                    winningRecordDO.getWinnerPhoneNumber().getValue(),
                    JacksonUtil.writeValueAsString(map));
        }
    }

    /**
     * 状态扭转
     *
     * @param param
     */
    private void statusConvert(DrawPrizeParam param) {

        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.COMPLETED);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.COMPLETED);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.COMPLETED);
        activityStatusManager.handlerEvent(convertActivityStatusDTO);
    }

//    private void statusConvert(DrawPrizeParam param) {

    // 问题：
    // 1. 活动状态扭转有依赖性（与其他状态有关）
    // 2. 状态扭转条件可能会扩展，当前写法扩展性差，维护性差
    // 3. 代码的灵活性、扩展性、维护性极差
    // 解决方案：设计模式（责任链设计模式 + 策略模式）

    // 活动：RUNNING --> RUNNING ?? x 全部奖品抽完后才改变状态
    // 奖品：INIT --> COMPLETED
    // 人员列表：INIT --> COMPLETED


    // 1 扭转奖品状态
    //  查询活动关联的奖品信息
    //  条件判断是否符合扭转奖品状态，判断当前状态是否 不为 COMPLETED，如果不是，需要扭转
    //  扭转

    // 2 扭转人员状态
    //  查询活动关联的人员信息
    //  条件判断是否符合扭转人员状态，判断当前状态是否 不为 COMPLETED，如果不是，需要扭转
    //  扭转

    // 3 扭转活动状态（必须在扭转奖品状态之后完成）
    //  查询活动信息
    //  条件判断是否符合扭转活动状态，判断当前状态是否 不为 COMPLETED，且全部奖品抽完之后，才改变状态
    //  扭转

    // 4 更新活动完整信息缓存
//    }
}
